package Shifu.MainServer.Management;

import Shifu.MainServer.Tools.*;
import Shifu.MainServer.Net.*;
import java.io.*;
import java.util.List;
import java.util.Iterator;
import java.rmi.RemoteException;

/**
 * Gestisce lo spezzettamento, la distribuzione e
 * la ricomposizione dei file
 */

public class Obreros implements Serializable {


	/* campi */

	private FileTable filetable;

	private PeersRing ring;

	private Sonar sonar;


	/* costruttore */

	/**
	 * Instanzia l'oggetto Obreros
	 * @param filetable la tabella dei file
	 * @param ring l'anello dei peers
	 */

	public Obreros(FileTable filetable,PeersRing ring) {
		this.filetable = filetable;
		this.ring = ring;
		this.sonar = new Sonar(ring,this);
		/*avvia il sonar*/
		this.startSonar();
	}		

	/* metodi */

	/**
	 * Setta la tabella dei file
	 * @param filetable la nuova tabella dei file
	 */

	public void setFileTable(FileTable filetable) {
		this.filetable = filetable;
	}

	/**
	 * Setta l'anello dei peers
	 * @param ring il nuovo anello
	 */

	public void setPeersRing(PeersRing ring) {
		this.ring = ring;
	}

	/**
	 * Setta il sonar
	 * @param sonar il nuovo sonar
	 */

	public void setSonar(Sonar sonar) {
		this.sonar = sonar;
	}

	/**
	 * Restituisci la tabella dei file
	 * @return la tabella dei file
	 */

	public FileTable getFileTable() {
		return this.filetable;
	}

	/**
	 * Restituisce l'anello dei peers
	 * @return l'anello dei peers
	 */

	public PeersRing getPeersRing() {
		return this.ring;
	}

	/**
	 * Restituisce il Sonar
	 * @return il sonar
	 */

	public Sonar getSonar() {
		return this.sonar;
	}

	/**
	 * Inserisce un peer nell'anello
	 * @param peer il peer da inserire
	 */

	public int insertPeer(Peer peer) {

			int i = ring.insertPeer(peer);

			if (i != 1) {

				System.out.println("Obreros: Errore nell inserimento del peer : "+peer.getId());
				return -1;
			}

			return 1;
	}

	/**
	 * Rimuove un peer dall'anello
	 * @param peer il peer da rimuovere
	 */

	public int removePeer(Peer peer) {
			
			filetable.removePeerTable(peer);	//rimuove il peer da ogni associazione chunk
			int i = ring.removePeer(peer);		//rimuove il peer del ring
			
			if (i != 1) {
 				System.out.println("Obreros: Errore nella rimozione del peer : "+peer.getId());
				return -1;
			}

			return 1;
	}


	/**
	 * Avvia il sonar
	 */

	public void startSonar() {
		System.out.println("Obreros: Avvio del sonar !");	
		sonar.start();
	}


	/**
	 * Ferma il sonar
	 */

	public void stopSonar() {
		System.out.println("Obreros: Arresto del sonar ! ");	
		sonar.stop();
	}


	/**
	 * Restituisce una stringa che descrive la tabella dei file
	 * @return elenco dei file presenti
	 */

	public String getFileList() {
		return  filetable.toString();
	}	

	/**
         * Restituisce la lista dei Peers connessi al sistema
         * @return la lista dai peers
         */

	public String getPeerList() {
		return ring.getPeerList();
	}


	/**
	 * Distribuisce un file nel sistema 
	 * @param fd il filedescriptor generato dal client
	 * @param buffer il vero file
	 * @return 1 se l'operazione è riuscita. -1 altrimenti
	 * @exception RemoteException se avvengono degli errori di trasmissione
	 */

	public int storageFile(SFileDescriptor fd,byte[] buffer) {
				
			File newfile = null;
			FileOutputStream out = null;
			FileChunkedDescriptor fcd = null;

			try {
				newfile = new File(fd.getFileName());
				newfile.createNewFile(); 
				out = new FileOutputStream(fd.getFileName());
				out.write(buffer); //scrive il file originale
				out.close();
				System.out.println("Obreros: Controllo MD5 del file recevuto ");
				/*controlla l'md5 del file originale*/
				if(!FileTools.check_MD5(newfile,fd.getHash())) {
					System.out.println("Obreros: Controllo MD5 fallito! remote hash: "+fd.getHash()+" local hash: "+FileTools.generate_MD5(newfile));
					return -1;
				}

				if(ring.size() == 0) {
					System.out.println("Obreros: Impossibile eseguire l'upload del file nessun peer disonibile");
					return -1;
				}
			
				//Split del file e invio dei chunk
				fcd = FileTools.fileSplit(newfile,ring.size());
				
				//itero sul numero di chunk creati
				List listachunk = fcd.getElenco_chunk();
				Iterator it = listachunk.iterator();

				int failurecount = 0;	//conta il numero di fallimenti successivi
				
				//Fino a che ci sono chunkDescriptor 
				while(it.hasNext()) {
					ChunkDescriptor chunk = (ChunkDescriptor) it.next();
					int dim = (int) chunk.getDimChunk();
					buffer = new byte[dim];
					FileInputStream in = new FileInputStream(chunk.getChunkName());
					in.read(buffer);
					
					//numero di copie da fare del chunk
					int clone = 0;

					
					while(clone < 3) {	//Impostato a 3 copie
						Peer currentpeer = ring.getNextPeer();
						System.out.println("----------------------");
						System.out.println("Obreros: Invio del chunk : "+chunk.getChunkName()+" al peer : "+currentpeer.getId());
						PeerServer stubpeer = (PeerServer) currentpeer.getStub();
						int check = stubpeer.storeChunk(chunk,buffer);
						if(check > 0) {	//Se il chunck è stato salvato correttamente dal peer
							System.out.println("Obreros:  Chunk : "+chunk.getChunkName()+" inviato al peer : "+currentpeer.getId());
							chunk.addPeerChunk(currentpeer);
							clone++;
						} else {
							failurecount++;	//incrementa il contatore
							System.out.println("Obreros: Impossibile salvare il Chunck : "+chunk.getChunkName()+" sul peer : "+currentpeer.getId()+" passo al peers successivo, siamo a: "+failurecount+" fallimenti!");
							if (failurecount == 10) { //10 è il numero massimo di fallimenti possibili
								System.out.println("Obreros: Impossibile salvare il file : "+fd.getFileName()+" poichè sono falliti 10 tentativi");
								return -1;							
							}
						}
				
					}

					failurecount = 0; //azzeramento contatore fallimenti
				}
			
			} catch (RemoteException re) {
				System.out.println("Obreros: Qualche peer non ha risposto alla chiamata!");
			} catch (IOException e) {
				System.out.println("Obreros: Errore di IO in Obreros ");
				e.printStackTrace();
				return -1;
			} 
		
			finally {

				try {
					out.close();

				}catch (IOException e) {
					System.out.println("Obreros: Errore di IO in chiusura stream");
					e.printStackTrace();
				}
			
			}

			Record record = new Record(fd.getFileName(),FileTools.generate_MD5(newfile),fcd);	
			filetable.insert(record);	//Viene inserito il file nella filetable
			return 1;		
	}

	/**
	 * Recupera il file richesto dalla rete
	 * @param index, indice del file
	 * @return il file più il suo descrittore
         * @excepton RemoteException se avvengono degli errori di trasmissione
	 */

	public MergedFile loadFile(int index) throws RemoteException {
		
		/* Se il file non esiste ritorna null*/
		if (filetable.thereIsFile(index) == false) 
			return null;		

		Record record = filetable.get(index);
		FileChunkedDescriptor fcd = record.getFcd();
		/* Elenco dei chunk che compongo il file*/
		List chunklist = fcd.getElenco_chunk();
		System.out.println("Obreros: Elenco Chunk ottenuto ");

		Iterator it = chunklist.iterator();
		

		/* recupera ogni chunk */
		while(it.hasNext()) {
			
			ChunkDescriptor chunkdesc = (ChunkDescriptor) it.next();
			List peerchunklist = chunkdesc.getPeersList();
			System.out.println("Obreros: Elenco dei peers che hanno il chunk ottenuto, "+peerchunklist.size()+" peers hanno questo chunk !");

			int indexpeer = 0;
			byte[] buffer = null;

			/* Cerca di ottenere il chunk */
			while(buffer == null && indexpeer < peerchunklist.size() ) {


				Peer currentpeer = (Peer) peerchunklist.get(indexpeer);
				PeerServer stub = currentpeer.getStub();
				System.out.println("Obreros: Recupero del chunk: "+chunkdesc.toString()+" dal peer: "+currentpeer.toString());
				/* richiede il chunk al peer */
				buffer = stub.getChunk(chunkdesc);
				indexpeer++;

			}
			

			/* Se il buffer non è vuoto */
			if(buffer != null) {

				try {

					File newfile = new File(chunkdesc.getChunkName());
					FileOutputStream out = new FileOutputStream(chunkdesc.getChunkName());
					out.write(buffer);
					out.close();

				} catch (IOException e) {
					System.out.println("Obreros: Errore di IO nel load del file");
					e.printStackTrace();
					return null;
				}
			}
			
			else {
				System.out.println("Obreros: Errore Impossibile recuperare parti del File !! Il file verrà rimosso della tabella dei file");
				this.removeFile(index);	//rimuove il file
				return null;
			}

		}

		System.out.println("Obreros: Generazione del file originale !");
		//Merge tutti i file
		FileTools.Merge_Chunk(fcd);

		FileInputStream in = null;
		MergedFile mf = null;

		//Legge il file creato e lo incapsula in un oggetto MergedFile
		try {
			in = new FileInputStream(fcd.getFileName());
			int dim = (int) fcd.getDim_file();
			byte[] buffer = new byte[dim]; 
			in.read(buffer);		//legge tutto il file
			SFileDescriptor sfd = new SFileDescriptor(fcd.getFileName(),fcd.getHash());
			mf = new MergedFile(buffer,sfd);

		} catch(IOException e) {
			System.out.println("Obreros: Errore file merged non trovato");
			e.printStackTrace();
		}

		finally {
			try {
				in.close();
			}catch(IOException io) {
				System.out.println("Obreros: Nella chiusura del file");
			}
		} 
		return mf;	
	}

	/**
	 * Rimuove un file dal sistema
	 * @param index indice del file dal rimuovere
	 * @return 1 se l'operazione è avvenuta con successo -1 altrimenti
	 * @exception RemoteException se avvengono degli errori di trasmissione
	 */

	public int removeFile(int index) throws RemoteException {
			
		System.out.println("Obreros: Inizio rimozione del file:"+index);
		/* Se il file non esiste ritorna null*/
		if (!filetable.thereIsFile(index)) {
			System.out.println("Obreros: File da eliminare inesistente!");
			return -1;
		}
		
		Record record = filetable.get(index);
		
		FileChunkedDescriptor fcd = record.getFcd();
		/* Elenco dei chunk che compongo il file*/
		List chunklist = fcd.getElenco_chunk();
		System.out.println("Obreros: Elenco Chunk ottenuto per eliminazione");

		Iterator it = chunklist.iterator();	

		
		/* recupera ogni chunk */
		while(it.hasNext()) {
			
			ChunkDescriptor chunkdesc = (ChunkDescriptor) it.next();
			List peerchunklist = chunkdesc.getPeersList();
			System.out.println("Obreros: Elenco dei peers che hanno il chunk ottenuto, "+peerchunklist.size()+" peers hanno questo chunk !");

			Iterator init = peerchunklist.iterator();
			
			/* Per ogni peer che ha il chunk */
			while(init.hasNext()) {
				
				Peer currentpeer = (Peer) init.next();

				System.out.println("Obreros: Invio comando di rimozione chunk: "+chunkdesc.getChunkName()+" al peer: "+currentpeer.getId());
				/* ottiene il chunk */
				PeerServer peerstub = currentpeer.getStub();
				/* chiama il metodo remoto del PeerServer*/
				int result = peerstub.removeChunk(chunkdesc);						

			}	

		}	

		
		/* Rimuovo il file dalla filetable*/
		filetable.delete(index);
		System.out.println("Obreros: File: "+fcd.getFileName()+" rimosso dalla filetable !");		

		return 1;
	}	

}
